https://tools.ietf.org/html/draft-pantos-http-live-streaming-07
https://ffmpeg.org/ffmpeg.html
http://gstreamer.freedesktop.org/
In [ ]:
!ffmpeg -i LlamaDrama.mp4 -movflags faststart -b:v 256000 -maxrate 256000 -x264opts "fps=24:keyint=48:min-keyint=48:no-scenecut" -hls_list_size 0 -hls_time 4 -hls_base_url http://192.168.3.14:8000/low/ low/LlamaDrama.m3u8
In [ ]:
!ffmpeg -i LlamaDrama.mp4 -movflags faststart -b:v 512000 -maxrate 512000 -x264opts "fps=24:keyint=48:min-keyint=48:no-scenecut" -hls_list_size 0 -hls_time 4 -hls_base_url http://192.168.3.14:8000/medium/ medium/LlamaDrama.m3u8
In [ ]:
!ffmpeg -i LlamaDrama.mp4 -movflags faststart -b:v 1024000 -maxrate 1024000 -x264opts "fps=24:keyint=48:min-keyint=48:no-scenecut" -hls_list_size 0 -hls_time 4 -hls_base_url http://192.168.3.14:8000/high/ high/LlamaDrama.m3u8
In [1]:
from collections import namedtuple
from io import BytesIO
from requests import get
import m3u8
from time import time
from io import BytesIO
from subprocess import call
In [2]:
Stream = namedtuple('Stream',['bandwidth', 'uri'])
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:4
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:4.000000,
http://localhost:8000/high/LlamaDrama0.ts
#EXTINF:4.000000,
http://localhost:8000/high/LlamaDrama1.ts
#EXTINF:4.000000,
http://localhost:8000/high/LlamaDrama2.ts
#EXTINF:4.000000,
http://localhost:8000/high/LlamaDrama3.ts
...
#EXTINF:4.000000,
http://localhost:8000/high/LlamaDrama21.ts
#EXTINF:1.875000,
http://localhost:8000/high/LlamaDrama22.ts
#EXT-X-ENDLIST
@startuml skinparam style strictuml skinparam dpi 300
class HLS << iterable >> { ByteIO next() }
class StreamEngine {
Stream selectStream(speed)
}
HLS -right-> StreamEngine
@enduml
In [3]:
class HLS:
speed = 0 # Bits / second
i = 0
def __init__(self, uri):
self.selector = StreamEngine(uri)
def __iter__(self):
return self
def __next__(self):
stream = self.selector.selectStream(self.speed)
if self.i < len(stream.segments):
startTime = time()
buf = getSegment(stream.segments[self.i])
self.speed = round((buf.getbuffer().nbytes*8) / (time() - startTime))
print('%d bits/s' %self.speed)
self.i += 1
return buf
else:
raise StopIteration
In [4]:
class StreamEngine:
currentStream = None
streamM3 = None
streams = None
def __init__(self, uri):
self.streams = sorted([Stream(playlist.stream_info.bandwidth, playlist.uri)
for playlist in m3u8.load(uri).playlists])
self.currentStream = self.streams[0]
self.streamM3 = m3u8.load(self.currentStream.uri)
def selectStream(self, speed):
newStream = self.currentStream
for stream in self.streams:
if stream.bandwidth < speed:
newStream = stream
else:
break
if newStream != self.currentStream:
self.currentStream = newStream
self.streamM3 = m3u8.load(newStream.uri)
print('Changing Streams: New BitRate %d' %newStream.bandwidth)
return self.streamM3
In [5]:
def getSegment(segment):
buf = BytesIO()
r = get(segment.uri, stream=True)
for chunk in r.iter_content(chunk_size=2048):
if chunk:
buf.write(chunk)
return buf
Le contenu des segments reçu par HLS est "pipé" dans GStreamer
Même, si GStreamer possède un buffer d'entrée, nous utilisons quand même un buffer interne pour ne pas blocker un téléchargement lorsque le buffer de GStreamer est plein.
L'appel de player.stdin.write étant bloquant, ceci va servir de mécanisme de controlle de flux pour les requêtes HLS.
In [ ]:
from subprocess import Popen, PIPE, STDOUT
hls = HLS('http://192.168.3.14:8000/LlamaDrama.m3u8')
player = Popen("/usr/local/bin/gst-play-1.0 fd://0".split(), stdout=PIPE, stdin=PIPE)
for segment in hls:
player.stdin.write(segment.getvalue())